package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.xuecheng.api.content.model.dto.CourseBaseDTO;
import com.xuecheng.api.content.model.dto.CourseTeacherDTO;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.system.model.dto.CourseCategoryDTO;
import com.xuecheng.common.constant.CoursePubTemplateKey;
import com.xuecheng.common.domain.response.RestResponse;
import com.xuecheng.common.enums.content.CourseAuditEnum;
import com.xuecheng.common.enums.content.CourseModeEnum;
import com.xuecheng.common.exception.ExceptionCast;
import com.xuecheng.common.util.JsonUtil;
import com.xuecheng.content.agent.SystemApiAgent;
import com.xuecheng.content.common.constant.ContentErrorCode;
import com.xuecheng.content.convert.CoursePubConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.CourseMarket;
import com.xuecheng.content.entity.CoursePub;
import com.xuecheng.content.entity.CoursePubMsg;
import com.xuecheng.content.mapper.CoursePubMapper;
import com.xuecheng.content.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * 课程发布 服务实现类
 * </p>
 *
 * @author dongliang
 */
@Slf4j
@Service
public class CoursePubServiceImpl extends ServiceImpl<CoursePubMapper, CoursePub> implements CoursePubService {

    @Autowired
    private CourseBaseService courseBaseService;
    @Autowired
    private TeachplanService teachplanService;
    @Autowired
    private CourseMarketService courseMarketService;
    @Autowired
    private SystemApiAgent systemApiAgent;
    @Autowired
    private CoursePubService coursePubService;
    @Autowired
    private CoursePubMsgService coursePubMsgService;
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private CourseTeacherService courseTeacherService;

    @Value("${course.publish.exchange}")
    private String exchange;
    @Value("${course.publish.routingkey}")
    private String routingkey;

    /**
     * 课程预览
     *
     * @param courseBaseId 课程Id
     * @param companyId    公司Id
     * @return Map 数据模型 Map
     */
    @Transactional
    public Map<String, Object> previewCourse(Long courseBaseId, Long companyId) {
        //1构建CoursePub数据并保存
        CoursePub coursePub = generateCoursePub(courseBaseId, companyId, false);

        //2根据CoursePub内容构架DataMap数据
        Map<String, Object> dataMap = generateDataMap(coursePub);

        //3将Datamap进行返回
        return dataMap;
    }

    /*
     * 构建CoursePub数据并保存
     *   业务分析：
     *       1.判断关键数据
     *        courseId companyId
     *       2.判断业务数据
     *           课程基础信息
     *               判断是否存在
     *               判断是否是同一家机构
     *               判断是否删除
     *               判断审核状态：教学机构课程预览--未提交、审核未通过
     *           课程营销
     *               判断是否存在：根据courseid
     *           课程计划
     *               获得课程计划：根据courseId和companyId（树形结构）
     *           课程教师
     *               判断教师信息是否存在：一定要确保课程最少有一个教师信息
     *
     *           课程分类数据并完善Coursepub数据
     *               调用system服务获得课程分类的名称
     *
     *       3.保存课程发布数据
     *           CoursePub数据保存数据库中
     *               课程基础信息  课程营销  课程计划  课程教师
     *
     *       4.将coursePub数据返回
     *
     * */
    /**
     * 课程预览_1构建CoursePub数据并保存
     *
     * @param courseBaseId 课程id
     * @param companyId    公司id
     * @param isPublish    判断预览还是发布
     * @return CoursePub
     */
    private CoursePub generateCoursePub(Long courseBaseId, Long companyId, boolean isPublish) {

        /*1.判断关键数据
         *        courseId companyId
         *       2.判断业务数据
         *           课程基础信息
         *               判断是否存在
         *               判断是否是同一家机构
         *               判断是否删除
         *               判断审核状态：教学机构课程预览必须--未提交、审核未通过*/
        CourseBaseDTO courseBase = courseBaseService.getCourseBase(courseBaseId, companyId);
        String auditStatus = courseBase.getAuditStatus();
        if (isPublish) {
            if (!(CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus))) {
                ExceptionCast.cast(ContentErrorCode.E_120201);
            }
        } else {
            if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus) ||
                    CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus) ||
                    CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)) {
                ExceptionCast.cast(ContentErrorCode.E_120015);
            }
        }

        /*课程营销
                判断是否存在：根据courseid*/
        LambdaQueryWrapper<CourseMarket> marketWrapper = new LambdaQueryWrapper<>();
        marketWrapper.eq(CourseMarket::getCourseId, courseBaseId);
        CourseMarket courseMarket = courseMarketService.getOne(marketWrapper);
        /*课程计划
                获得课程计划：根据courseId和companyId（树形结构）*/
        TeachplanDTO teachplanTreeNodes = teachplanService.queryTreeNodesByCourseId(courseBaseId, companyId);

        /*课程教师
                判断教师信息是否存在：一定要确保课程最少有一个教师信息*/
        // TODO: 2021/12/17
        List<CourseTeacherDTO> courseTeacherDTOList = courseTeacherService.queryTeacherList(companyId, courseBaseId);



        /*
        课程分类数据并完善Coursepub数据
                调用system服务获得课程分类的名称 */
        String mt = courseBase.getMt();
        String st = courseBase.getSt();

        RestResponse<CourseCategoryDTO> mtResponse = systemApiAgent.getCourseCategoryById4s(mt);
        if (!(mtResponse.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(mtResponse.getCode(), mtResponse.getMsg());
        }
        RestResponse<CourseCategoryDTO> stResponse = systemApiAgent.getCourseCategoryById4s(st);
        if (!(stResponse.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(stResponse.getCode(), stResponse.getMsg());
        }
        CourseCategoryDTO mtEntity = mtResponse.getResult();
        CourseCategoryDTO stEntity = stResponse.getResult();


        // 构建coursePub数据并执行保存操作
        // 课程基础数据
        CoursePub coursePub = CoursePubConvert.INSTANCE.courseBase2coursePub(courseBase);

        // 课程营销数据
        String marketJsonString = JsonUtil.objectTojson(courseMarket);
        coursePub.setMarket(marketJsonString);
        coursePub.setPrice(courseMarket.getPrice());
        coursePub.setCharge(courseMarket.getCharge());

        // 课程计划数据
        String teachplanJsonString = JsonUtil.objectTojson(teachplanTreeNodes);
        coursePub.setTeachplan(teachplanJsonString);

        // 课程教师
        // TODO: 2021/12/17 学员完成
        String courseTeacherJsonString = JsonUtil.objectTojson(courseTeacherDTOList);
        coursePub.setTeachers(courseTeacherJsonString);


        // 课程分类数据
        coursePub.setMtName(mtEntity.getName());
        coursePub.setStName(stEntity.getName());


        // 3.保存课程发布数据
        //     CoursePub数据保存数据库中
        //         课程基础信息  课程营销  课程计划  课程教师
        // 一个courseBase数据对应一个coursePub数据
        LambdaQueryWrapper<CoursePub> pubQueryWrapper = new LambdaQueryWrapper<>();
        pubQueryWrapper.eq(CoursePub::getCourseId, courseBaseId);
        pubQueryWrapper.eq(CoursePub::getCompanyId, companyId);

        CoursePub po = this.coursePubService.getOne(pubQueryWrapper);

        boolean result = false;
        // 判断课程发布数据是否存在
        if (ObjectUtils.isEmpty(po)) {
            //     如果不存在
            //             创建课程发布数据
            //     创建课程发布数据时，要将coursePub和courseBase数据进行关联
            coursePub.setCourseId(courseBase.getCourseBaseId());
            result = this.coursePubService.save(coursePub);

        } else {
            //     如果存在
            //             修改课程发布数据内容
            coursePub.setId(po.getId());

            result = this.coursePubService.updateById(coursePub);

        }

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120205);
        }


        // 4.将coursePub数据返回
        return coursePub;
    }

    /**
     * 课程预览_2根据CoursePub内容构架DataMap数据
     *
     * @param coursePub coursePub
     * @return 数据模型
     */
    private Map<String, Object> generateDataMap(CoursePub coursePub) {

        // 1.构建数据模型对象
        HashMap<String, Object> dataMap = new HashMap<>();

        // 2.构建coursePub
        dataMap.put(CoursePubTemplateKey.COURSEPUB, coursePub);

        // 3.构建courseMarket
        String marketJsonString = coursePub.getMarket();
        CourseMarket courseMarket = JsonUtil.jsonToObject(marketJsonString, CourseMarket.class);

        dataMap.put(CoursePubTemplateKey.COURSEMARKET, courseMarket);

        // 4.构建课程计划
        String teachplanJsonString = coursePub.getTeachplan();
        TeachplanDTO teachplanDTO = JsonUtil.jsonToObject(teachplanJsonString, TeachplanDTO.class);
        dataMap.put(CoursePubTemplateKey.TEACHPLANNODE, teachplanDTO);

        // 5.构建课程模式
        CourseModeEnum[] values = CourseModeEnum.values();
        dataMap.put(CoursePubTemplateKey.COURSETEACHMODEENUMS, values);

        return dataMap;
    }


    /*
     * 业务分析：
     *   1.判断关键数据
     *   2.判断业务数据
     *       课程基本信息
     *           判断是否存在
     *           判断是否是同一家机构
     *           判断是否删除
     *           判断审核状态
     *               只能是审核通过的状态
     *   3.创建课程发布数据和发布消息数据
     *       课程发布数据
     *           课程基本信息 课程营销 课程计划 课程教师信息
     *       发布消息表
     *   4.发送消息给mq
     *       消息必须是有业务唯一标识：courseid
     *       发送消息：发布消息表
     *           转为json字符串
     *
     * */
    /**
     * 课程发布
     *
     * @param courseId  课程id
     * @param companyId 公司id
     */
    @Transactional
    public void publish(Long courseId, Long companyId, boolean isResent) {

        CoursePubMsg coursePubMsg = null;
        // 如果是课程发布定时器重新发送的操作，无需产生coursePub数据和coursepubMsg
        if (isResent) {
            LambdaQueryWrapper<CoursePub> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(CoursePub::getCourseId, courseId);
            queryWrapper.eq(CoursePub::getCompanyId, companyId);

            CoursePub coursePub = this.getOne(queryWrapper);

            coursePubMsg = new CoursePubMsg();

            coursePubMsg.setPubId(coursePub.getId());
            coursePubMsg.setCourseId(coursePub.getCourseId());
            coursePubMsg.setPubStatus(CoursePubMsg.UNSENT);
            coursePubMsg.setCompanyId(coursePub.getCompanyId());
            coursePubMsg.setPubName(coursePub.getName());

        } else {
            // 1.判断关键数据
            //  2.判断业务数据
            //      课程基本信息
            //          判断是否存在
            //          判断是否是同一家机构
            //          判断是否删除
            //          判断审核状态
            //              只能是审核通过的状态

            //  3.创建课程发布数据和发布消息数据
            //      课程发布数据
            //          课程基本信息 课程营销 课程计划 课程教师信息
            CoursePub coursePub = this.generateCoursePub(courseId, companyId, true);


            //      发布消息表
            coursePubMsg = new CoursePubMsg();

            coursePubMsg.setPubId(coursePub.getId());
            coursePubMsg.setCourseId(coursePub.getCourseId());
            coursePubMsg.setPubStatus(CoursePubMsg.UNSENT);
            coursePubMsg.setCompanyId(coursePub.getCompanyId());
            coursePubMsg.setPubName(coursePub.getName());


            // 为避免前端发送请求重复，查询消息表的数据是否存在
            LambdaQueryWrapper<CoursePubMsg> msgQueryWrapper = new LambdaQueryWrapper<>();
            msgQueryWrapper.eq(CoursePubMsg::getPubId, coursePub.getId());

            int count = coursePubMsgService.count(msgQueryWrapper);

            if (count < 1) {
                boolean result = coursePubMsgService.save(coursePubMsg);

                if (!result) {
                    ExceptionCast.cast(ContentErrorCode.E_120206);
                }
            }
        }

        //  4.发送消息给mq
        //      消息必须是有业务唯一标识：courseid
        //      发送消息：发布消息表
        //          转为json字符串

        // 设置mq的ack通知
        CorrelationData correlationData = new CorrelationData(courseId.toString());

        correlationData.getFuture().addCallback(
                /* 交互机响应成功的回调方法 */
                confirm -> {
                    // 交互机响应成功
                    if (confirm.isAck()) {
                        // 修改课程基本信息审核状态和发布消息表的状态
                        execChangeStatusData(new Long(correlationData.getId()));
                    }
                },
                /* 交互机响应失败的回调方法 */
                throwable -> {
                    log.error("课程发布消息通知失败,courseId:{},exception:{}", courseId, throwable.getMessage());
                }
        );
        String msgJsonString = JsonUtil.objectTojson(coursePubMsg);

        rabbitTemplate.convertAndSend(exchange, routingkey, msgJsonString, correlationData);
    }

    /*
    * 消息成功发送并修改课程相关数据
        课程基本信息的审核状态
        发布消息表的状态
    * */
    /**
     * 消息成功发送后修改课程审核状态,发布消息表的状态
     * @param courseId 课程id
     */
    @Transactional
    public void execChangeStatusData(Long courseId) {

        // 1.课程基本信息的审核状态
        LambdaUpdateWrapper<CourseBase> baseUpdateWrapper = new LambdaUpdateWrapper<>();

        baseUpdateWrapper.set(CourseBase::getAuditStatus, CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode());
        baseUpdateWrapper.set(CourseBase::getChangeDate, LocalDateTime.now());
        baseUpdateWrapper.eq(CourseBase::getId, courseId);

        boolean baseResult = courseBaseService.update(baseUpdateWrapper);

        if (!baseResult) {
            log.info("课程基本信息修改发布后的状态失败，courseId：{}", courseId);
        }

        // 发布消息表的状态
        LambdaUpdateWrapper<CoursePubMsg> msgUpdateWrapper = new LambdaUpdateWrapper<>();

        msgUpdateWrapper.set(CoursePubMsg::getPubStatus, CoursePubMsg.SENT);
        msgUpdateWrapper.eq(CoursePubMsg::getCourseId, courseId);

        boolean msgResult = coursePubMsgService.update(msgUpdateWrapper);

        if (!msgResult) {
            log.info("课程发布消息数据修改发布后的状态失败，courseId：{}", courseId);
        }
    }
}